package com.ruoyi.web.utils.wechat;

import com.github.wxpay.sdk.WXPayConfig;
import com.github.wxpay.sdk.WXPayUtil;

import com.ruoyi.web.core.config.*;
import com.ruoyi.web.utils.AESUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Map;

/**
 * 微信工具类
 */
public class WechatUtils {

    private static Logger logger = LogManager.getFormatterLogger(WechatUtils.class);

    static final int TAG_LENGTH_BIT = 128;

    /**
     * 获取请求对象中ip地址
     * @param req 请求对象
     * @return ip
     */
    public static String getIpAddr(HttpServletRequest req) {
        String ip = req.getHeader("X-Forwarded-For");
        if (ip != null && !"unKnown".equalsIgnoreCase(ip)) {
            int index = ip.indexOf(",");
            if (index != -1) {
                return ip.substring(0, index);
            } else {
                return ip;
            }
        }
        ip = req.getHeader("X-Real-IP");
        if (ip != null && !"".equals(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            return ip;
        }
        return req.getRemoteAddr();
    }


    /**
     * 获取天蛙科技配置文件 获取失败返回null
     * @return WXPayConfig
     */
    public static WXPayConfig getTimvvaConfig() {
        try {
            return new TimvvaPayConfig();
        } catch (IOException e) {
            logger.error("【error】：天蛙科技配置文件初始化失败" +e.getMessage(), e);
        }
        return null;
    }

    /**
     * map转化为xml文件 转化失败返回 fail
     * @param map map集合
     * @param key 商户密钥
     * @return xml
     */
    public static String mapToXml(Map<String, String> map, String key) {
        try {
            return WXPayUtil.generateSignedXml(map, key);
        } catch (Exception e) {
            logger.error("【error】：map集合转化xml失败" +e.getMessage(), e);
        }
        return "fail";
    }

    /**
     * xml字符串转化map集合 转化失败返会null
     * @param xml
     * @return
     */
    public static Map<String, String> xmlToMap(String xml) {
        try {
            return WXPayUtil.xmlToMap(xml);
        } catch (Exception e) {
            logger.error("【error】：xml转化map集合失败" +e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取微信支付签名数据
     * @param map map 加密信息
     * @param key key 密钥
     * @return String 签名
     */
    public static String generateSignature(Map<String, String> map, String key) {
        try {
            return WXPayUtil.generateSignature(map, key);
        } catch (Exception e) {
            logger.error("【error】：签名转化失败" +e.getMessage(), e);
        }
        return "fail";
    }

    /**
     * 获取微信xml带签名
     * @param map 转化数据
     * @param key 密钥
     * @return String
     */
    public static String generateSignedXml(Map<String, String> map, String key){
        try {
            return WXPayUtil.generateSignedXml(map,key);
        } catch (Exception e) {
            logger.error("【error】：微信支付参数map集合转化xml异常，",e.getMessage(),e);
            return "fail";
        }
    }

    /**
     * 获取请求流中的数据请求数据
     * @param req 请求对象
     * @return String
     */
    public static String getReqStreamContent(HttpServletRequest req) {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
            String line ;
            StringBuffer sb = new StringBuffer();
            while ((line = br.readLine()) != null){
                sb.append(line);
            }
            br.close();
            return sb.toString();
        } catch (IOException e) {
            logger.error("【error】：获取请求对象参数失败 " +e.getMessage(), e);
        }
        return "fail";
    }

    /**
     * 将数据进行 SHA256 with RSA签名
     * @param data 签名数据
     * @param key 秘钥
     * @return
     */
    public static String HMACSHA256(String data, String key) {
        try {
            return WXPayUtil.HMACSHA256(data,key);
        } catch (Exception e) {
            logger.error("【error】：SHA256 with RSA签名失败 " +e.getMessage(), e);
        }
        return "fail";
    }

    /**
     * 数据进行 SHA256withRSA 并将之转化为base64数据
     * @param data 数据
     * @param key 密钥
     * @return
     */
    public static String sha256withRSA(String data, String key) throws Exception {
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(getPrivateKey(key));
        signature.update(data.getBytes("utf-8"));
        return Base64.getEncoder().encodeToString(signature.sign());
    }

    /**
     * 获取私钥对象
     * @param key 秘钥信息
     * @return
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String key) throws Exception {
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePrivate( new PKCS8EncodedKeySpec(Base64.getDecoder().decode(key)));
    }

    /**
     * 获取微信api v3请求验证头 Authorization
     * @param method http请求方法  大写
     * @param url 请求地址 不含域名
     * @param body 请求报文主体  无填写 ""
     * @param privateKey 请求
     * @param mchid 商户号
     * @param serialNo 证书序列号
     * @return
     */
    public static String getToken (String method, String url, String body, String privateKey, String mchid, String serialNo){
        try {
            String nonceStr = WXPayUtil.generateNonceStr();
            long timestamp = System.currentTimeMillis() / 1000;
            String msg = buildMessage(method, url, timestamp, nonceStr, body);
            return "mchid=\"" + mchid + "\","
                    + "nonce_str=\"" + nonceStr + "\","
                    + "timestamp=\"" + timestamp + "\","
                    + "serial_no=\"" + serialNo + "\","
                    + "signature=\"" + sha256withRSA(msg,privateKey) + "\"";
        } catch (Exception e) {
            logger.error("SHA256withRSA 转义失败",e.getMessage(),e);
        }
       return "fail";
    }

    /**
     * 构造签名串
     * @param method http请求方法
     * @param url URL地址
     * @param timestamp 请求时间戳
     * @param nonceStr 随机字符串
     * @param body 请求报文主体
     * @return
     */
    public static String buildMessage(String method, String url, long timestamp, String nonceStr, String body){
        return  method + "\n"
                + url + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }

    /**
     * AES256 解密数据解密
     * @param data 数据密文
     * @param key 秘钥  apiv3
     * @param iv 偏离值
     * @return
     */
    public static String aes256Decrypt(String data, String key, String iv) {
        try {
            return AESUtils.decrypt(data,key,iv);
        } catch (Exception e) {
            logger.error("AES256 解密失败",e.getMessage(),e);
        }
        return "fail";
    }


    /**
     * 微信 AEAD_AES_256_GCM 解码
     * @param associatedData 附加数据
     * @param nonce 随机串
     * @param ciphertext 数据密文
     * @param key qpiv3值
     * @return
     */
    public static String weChatAPIV3Aes(String associatedData, String nonce, String ciphertext,String key) {
        byte[] associatedDataByte = associatedData.getBytes();
        byte[] nonceByte = nonce.getBytes();
        byte[] keyByte = key.getBytes();
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec secretKeySpec = new SecretKeySpec(keyByte, "AES");
            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonceByte);
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, spec);
            cipher.updateAAD(associatedDataByte);
            return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
        } catch (Exception e) {
            logger.error("【error】---> 微信回调解密失败："+e.getMessage(),e);
        }
        return "fail";
    }

   /* public static void main(String[] args) {
        String associatedData = "payscore";
        String nonce = "QMJ1yphbBHvL";
        String ciphertext = "o0Pgl/5LeD9LGg20ikNrveru4tiun6FB1Ta/X9j3Zu/XyE99tFQOcXqoSdHE9CrWxXzd71iPeaeVJ/VFzmkBRX5TCOvxgySEkXhwepbdGqsHtO59hCa1mHxli2ZpBdJEqxK2cboWapjRj/9GYi6G4QPr14Wxko5pKRnB1my2Bxrx1zD+nK7X/8kDV7winjFwrzpMT44rxJtqKFe7jSZisI7VbOYmdrsjcZWBSDuwTGBnDo5MKOpKbddwiXBZkjDOPbXRE6CFULsjS7ePE4SsqhKyh18+27cv8fSHW4CuMTZ3alLv9Vj7vQYqrEoU/zIm/RK2FO9mHzfUlDXjaIsid2bfNjV3Wpq65OmuWTIfIdQISRRsfFP0nThJifkfgmjp48O3y2xlNYW++I+mKI7n0w9A/y7AjvC1iFz/ZLGmER6gkvyEoIt970s2HAgiHRGt6DulIQKt6vS4fuXNtgXF5TkYMbX9c/ObbiQLNEuCch/XLMV/AT4mCLZMKrdmkeIW/BrnXyrb5jdoZRQBZ+mdqUtXkyoS8aTahmUnI5t/YzS6HogsidTQFcOmZGZfqdj6tBvdVbaZXdHRTqDBZ4nq0dSuzqjmPOJ8w23I/JuBjITcnQJXXLvvDzGnnyEE+ttpHYTreDp1VcjKBZkAy0MWCuZSqDDSpKs5dIiMnW4HsTtaX+HoLkPZgvlolGTP78gdA0XpkEuL446aT3X9KrHbnQSmjBiIzBhqRujQdZQPkk5XFv7esb4jS1exnpB1gLiBddzCMbj2jkgbbnZVQ/rlPVCnqMyw2c36aTfqlpNoS2qOndBr724gBLE3S4ehWBXoC30ZZFAzo9WXqxJRzrazqEdNpFYN5Gi0bbDnrQ41H6o/n7Gx2uZCiG1mDQ0BVw/0tLXSuxjMrlSCK7utPvETvWuwpEWYniC2dgGfW3UD67Aoh0Q3TV6LbRQ=";
        System.out.println(weChatAPIV3Aes(associatedData,nonce,ciphertext, TimvvaConfig.API_V3_KEY));
    }*/

    /**
     * 根据商户号获取配置
     * @param mchid
     * @return
     */
    public static WechatConfig getWechatConfig(String mchid){
        if ("1600285034".equals(mchid)){
            return new TimvvaConfig();
        }else if ("1602299358".equals(mchid)){
            return new HelloConfig();
        }else if ("1607299523".equals(mchid)){
            return new WamiConfig();
        }else if ("1607391587".equals(mchid)){
            return new YdcxxConfig();
        }else if ("1610622133".equals(mchid)){
            return new ZhongshenConfig();
        }else if ("1610603356".equals(mchid)){
            return new BiaomaConfig();
        }else if ("1508382801".equals(mchid)){
            return new YdcxConfig();
        }else {
            logger.error("【error】---> 商户号不存在，mchid："+mchid);
            return null;
        }
    }


}
